; 读取内存信息，使用BIOS提供的中断0x15h
; 中断的具体用法如下
; 入参介绍：
;   eax = 0e820h，中断调用子功能号
;   ebx = 上一次调用后地址范围描述符的计数值，如果是首次调用，该值必须为0
;   es:di = 存放地址范围描述符，调用后BIOS会填充到此处
;   ecx = es:di指向的内存区域大小，以字节为单位，通常是20字节
;   edx = 0534D4150h，固定为该值，实际是ASCII "SAMP"
; 出参介绍：
;   CFLAGS寄存器中，CF = 0表示调用成功，CF = 1表示调用出错
;   eax = 0534D4150h
;   es:di = 入参位置，由BIOS向此处填充ecx字节
;   ecx = BIOS在es:di位置填充的地址范围描述符的字节数
;   ebx = 获取下一个地址描述符所需要的计数值，再次调用时直接使用即可，如果调用返回的ebx值为0，表示已经获取完成（前提是没有出错）

; 地址范围描述符结构：共20字节
;   dd - 基地址的低32位
;   dd - 基地址的高32位
;   dd - 长度的低32位（单位是字节）
;   dd - 长度的高32位（单位是字节）
;   dd - 地址范围的类型，类型有三个取值：
;           1 : AddressRangeMemory，可以被操作系统使用的内存
;           2 : AddressRangeReserved，正在被使用或保留，操作系统不能使用
;           其他值 : 未定义，操作系统要当做AddressRangeReserved处理
jmp LABEL_BEGIN                 ; 注意，这个语句必须是首行

%include "pm.inc"


; 这里定义一个区域用来存放内存信息，暂定512字节
_Memory_Info_Buffer: times 512 db 0

_Memory_Count: db 0

AddressDescriptionSize  equ     20                  ; 地址结构描述符大小（单位字节）
SegBase                 equ     10000h              ; 段基址，从64K处开始
SegLimit                equ     0C00000h - 010000h  ; 段长度：12M-4K
TopOfStack              equ     0A000h              ; 堆栈底 

[SECTION .gdt]
; GDT描述符
; 每一项的格式: 段基址， 段界限，属性
LABEL_GDT:          Descriptor      0,                  0,                  0                       ; 空描述符
LABEL_DESC_CODE32:  Descriptor      SegBase,            SegLimit - 1,       DA_C + DA_32            ; 32位非一致代码段
LABEL_DESC_VIDEO:   Descriptor      0B8000h,            0ffffh,             DA_DRW                  ; 显存地址段（可读可写数据段）
LABEL_DESC_DATA:    Descriptor      SegBase,            SegLimit - 1,       DA_DRW                  ; 数据段
LABEL_DESC_STACK:   Descriptor      SegBase,            TopOfStack - 1,     DA_DRWA + DA_32         ; 32位堆栈段

; GDT结束

GdtLen equ $ - LABEL_GDT    ; GDT长度
GdtPtr dw GdtLen - 1        ; GDT界限
       dd 0                 ; GDT基地址


; GDT段选择符
SelectorCode32      equ     LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo       equ     LABEL_DESC_VIDEO - LABEL_GDT
SelectorData        equ     LABEL_DESC_DATA - LABEL_GDT
SelectorStack       equ     LABEL_DESC_STACK - LABEL_GDT

; End of GDT SECTION


[SECTION .code16]
[BITS 16]
LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h

    call GetMemoryInfo

    ; 为加载GDTR做准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT  ; gdt基地址 ---> eax
    mov dword [GdtPtr + 2], eax ; gdt基地址 ---> [GdtPtr + 2]

    ; 加载GDTR
    lgdt [GdtPtr]

    ; 关中断 保护模式下的中断处理机制和实模式下不一样，先简单关中断处理
    cli

    ; 打开地址线A20，历史原因，打开它才能访问超过1M的地址空间
    in al, 92h
    or al, 00000010b
    out 92h, al

    ; 准备切换到保护模式
    mov eax, cr0
    or eax, 1       ; CR0的最低位是PE，该值为1时表示启动分段保护模式，开启PE=1模式后，必须立马使用跳转命令，例如下面的jmp命令
    mov cr0, eax

    ; 真正进入保护模式
    jmp dword SelectorCode32:LABEL_CODE32_ENTRY

GetMemoryInfo:
    mov di, _Memory_Info_Buffer
    mov ebx, 0          ; 首次调用，值为0
.loop:
    mov eax, 0e820h     
    mov ecx, AddressDescriptionSize
    mov edx, 0534D4150h

    int 015h

    ; 判断CF标记位
    jc .error           ; CF = 1，出错

    add byte [_Memory_Count], 1

    cmp ebx, 0
    je .finished        ; 没有出错，且ebx=0表示已经获取完成       

    ; 还未获取结束，则需要继续获取
    add di, AddressDescriptionSize
    jmp .loop

.error:
    jmp $

.finished:
    ; 获取成功，则需要打印出来
    ret

; End of Code16


Title1: db "AdressLow   "
Title1Len equ $ - Title1
Title2: db "AdressHigh  "
Title2Len equ $ - Title2
Title3: db "LengthLow   "
Title3Len equ $ - Title3
Title4: db "LengthHigh  "
Title4Len equ $ - Title4
Title5: db "Type        "
Title5Len equ $ - Title5

[SECTION .code32]
[BITS 32]
LABEL_CODE32_ENTRY:         ; 保护模式入口
    ; 进入保护模式后，其他段选择符重新加载
    mov ax, SelectorVideo
    mov gs, ax
    mov ax, SelectorData
    mov ds, ax
    mov ax, SelectorStack
    mov ss, ax
    mov esp, TopOfStack

    call CleanScreen

    ; 初始化显示位置
    mov edi, (80 * 0 + 0) * 2

    mov al, byte [_Memory_Count]
    call DispByteByHex
    call DispReturn

    mov eax, Title1
    mov ecx, Title1Len
    call DispStr
    mov eax, Title2
    mov ecx, Title2Len
    call DispStr
    mov eax, Title3
    mov ecx, Title3Len
    call DispStr
    mov eax, Title4
    mov ecx, Title4Len
    call DispStr
    mov eax, Title5
    mov ecx, Title5Len
    call DispStr

    call DispReturn

    mov esi, _Memory_Info_Buffer
    xor ecx, ecx
    mov cl, byte [_Memory_Count]
.memory_counts:
    call ShowOneInt
    add esi, 4
    call ShowOneInt
    add esi, 4
    call ShowOneInt
    add esi, 4
    call ShowOneInt
    add esi, 4
    call ShowOneInt
    add esi, 4
    call DispReturn
    loop .memory_counts 

    jmp $

ShowOneInt:
    mov eax, dword [ds:esi]
    call DispInt
    call DispSpace
    call DispSpace
    call DispSpace
    call DispSpace
    ret

; End of Code32


%include "lib.inc"
